<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html><head><title></title><style id="style" type="text/css" rel="stylesheet"></style><script type="text/javascript">

/* ***** BEGIN LICENSE BLOCK *****
 * Version: GNU GPL 3.0
 *
 * The contents of this file are subject to the
 * GNU General Public License Version 3.0; you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * http://www.gnu.org/licenses/gpl.html
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 * ***** END LICENSE BLOCK ***** */



var CONNECTION_ERROR = 'CONNECTION_ERROR';

var _body, _html, _doc, _win = window;
var $M = {}; // modules
var $A = {}; // public api
var $C = { xhrPoolSize:2, maxRetry:3 }; // configuration

$A.NewXMLHttpRequest = _win.ActiveXObject ? function() { return new ActiveXObject('Microsoft.XMLHTTP') } : function() { return new _win.XMLHttpRequest() };
$A.AddEvent = _win.attachEvent ? function( elt, ev, handler ) { elt.attachEvent( 'on'+ev, handler ) } : function( elt, ev, handler ) { elt.addEventListener( ev, handler, false ) };
$A.RemoveEvent = _win.detachEvent ? function( elt, ev, handler ) { elt.detachEvent( 'on'+ev, handler ) } : function( elt, ev, handler ) { elt.removeEventListener( ev, handler, false ) };
$A.PreventDefault = function(ev) { ev.preventDefault ? ev.preventDefault() : (ev.returnValue = true) };
$A.StopPropagation = function(ev) { ev.stopPropagation ? ev.stopPropagation() : (ev.cancelBubble = false) };
$A.InsertHtmlBefore = function(elt, html) {

	var tmp = _doc.createElement('div');
	tmp.innerHTML = html;
	while ( tmp.firstChild )
		elt.parentNode.insertBefore( tmp.firstChild, elt );
}

$A.InsertHtmlInside = function(elt, html) {

	var tmp = _doc.createElement('div');
	tmp.innerHTML = html;
	while ( tmp.firstChild )
		elt.appendChild( tmp.firstChild );
}

$A.SetStyle = function(style) {

	var e = _doc.getElementById('style');
	if ( e.styleSheet ) { // IE
		e.styleSheet.cssText = style;
		_win.resizeBy(0,-1); _win.resizeBy(0,1); // force GUI refresh
	} else  {
		for( ;e.firstChild; e.removeChild(e.firstChild)); // cleanup
		e.appendChild(_doc.createTextNode(style)); // || firefox: e.innerHTML = style;
	}
}

$A.SetTitle = function(title) {

	_doc.title = title + ' - ';
}

$A.HttpRequest = function(OnResponse) {

	var _this = this;
	var xhr = $A.NewXMLHttpRequest();
	this.Ready = function() {

		return xhr.readyState == 0 || xhr.readyState == 4;
	}

	this.Close = function() {

		xhr.onreadystatechange = function(){};
		if ( this.Ready() )
			return;
		if ( 'abort' in xhr )
			xhr.abort();
		else
			xhr.open('GET', '/', true);
	}

	this.Send = function(url, requestData, requestType) {

		var status, responseData, responseType;
		try {
			xhr.open( requestData == undefined ? 'GET' : 'POST', url, true );
			xhr.onreadystatechange = function() {

				try {
					if ( xhr.readyState != 4 )
						return;
					xhr.onreadystatechange = function(){};
					status = xhr.status;
					responseData = xhr.responseText;
					responseType = xhr.getResponseHeader('Content-Type');
				} catch(ex) {}
				OnResponse && OnResponse(_this, status, responseData, responseType, url, requestData, requestType);
			}
			if (requestType != undefined)
				xhr.setRequestHeader('Content-Type', requestType);
			xhr.send(requestData == undefined ? null : requestData);
		} catch(ex) {

			xhr.onreadystatechange = function(){};
			OnResponse && OnResponse(_this, status, responseData, responseType, url, requestData, requestType);
		}
	}
}

$A.AddModule = function(moduleConstructor) {

	var module = new moduleConstructor($A);
	$M[module.name] = module;
	module.OnAdd && module.OnAdd();
}

$A.RemoveModule = function(name) {

	$M[name].OnRemove && $M[name].OnRemove();
	delete $M[name];
}

$A.Dispatch = function(call /*, ...*/) {

	for ( var moduleName in $M )
		$M[moduleName][call] && $M[moduleName][call].apply($M[moduleName], arguments);
}


function CoreModule() {

	this.name = 'core';

	function EncodeArray(arr) {

		var str = '';
		for (var i=0; i < arr.length; i++)
			str += (i ? '&' : '') + (arr[i] ? encodeURIComponent(arr[i]) : '');
		return str;
	}

	function DecodeArray(str) {

		var arr = str.split('&');
		for (var i in arr)
			arr[i] = decodeURIComponent(arr[i]);
		return arr;
	}

	function DispatchResponse(data, type) {

		if ( type != 'application/x-www-form-urlencoded' )
			return;
		var tmp = DecodeArray(data);
		for ( var i in tmp )
			$A.Dispatch.apply(this, DecodeArray(tmp[i]));
	}

	var maxRetry = $C.maxRetry;
	var retry = 0;
	var sendingQueue = [];

	var xhrList = [];
	for (var i=0; i<$C.xhrPoolSize; i++) // (TBD) make configurable
		xhrList.push(new $A.HttpRequest(OnResponse));
	var xhrPool = xhrList.slice();

	function OnResponse(xhr, status, responseData, responseType, url, requestData, requestType) {

		xhrPool.push(xhr);
		responseData && DispatchResponse(responseData, responseType);
		if (status == 200)
			retry = 0;
		else
			if (++retry >= maxRetry) {

				$A.Dispatch(CONNECTION_ERROR);
				return;
			}

		if (sendingQueue.length || xhrPool.length == 2)
			_win.setTimeout($A.Send, retry ? retry*500 : 0);
	}

	$A.Send = function(message) {

		message && sendingQueue.push(message);

		if ( xhrPool.length ) {

			var data;
			if (sendingQueue.length) {

				data = [];
				for ( var i in sendingQueue )
					data.push(EncodeArray(sendingQueue[i]));
				data = EncodeArray(data);
				sendingQueue.splice(0,sendingQueue.length);
			}
			xhrPool.shift().Send('?action=submit', data, 'application/x-www-form-urlencoded');
		}
	}

	$A.Send(); // pending request

	this.OnRemove = function() {

		delete $A.Send;
		while (xhrList.length)
			xhrList.shift().Close(); // IE seems to not close pending XHR request on page reload.
	}

	this.AddModule = function( _, moduleSrc ) {

		$A.AddModule(Function(moduleSrc));
	}
}


$A.AddEvent( _win, 'load', function() {

	_doc = _win.document;
	_html = _doc.documentElement;
	_body = _doc.body;
	$A.RemoveEvent( _win, 'load', arguments.callee );
	$A.AddModule(CoreModule);
	$A.AddEvent( _win, 'unload', function() {

		$A.RemoveEvent( _win, 'unload', arguments.callee );
		$A.RemoveModule('core');
	});
});

</script></head><body></body></html>
